﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using LiveNet.Authentication;
using System.Drawing;
using System.Globalization;

namespace LiveNet.Photos
{
    public sealed partial class LivePhotos : LiveNetBase<DefaultRequestCreator, DefaultAuthenticationDetails>
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="LivePhotos"/> class.
        /// </summary>
        /// <param name="userId">The user id (Windows Live ID).</param>
        /// <param name="token">The token (DAT/RPS).</param>
        /// <param name="tokenType">Authentication type</param>
        /// <example>
        /// <code>
        /// // Create the instance of LivePhotos
        /// LivePhotos m_spaces = new LivePhotos("example@hotmail.com", DAT, AuthenticationToken.DomainAuthenticationToken);
        /// </code>
        /// </example>
        public LivePhotos(string userId, string token, AuthenticationToken tokenType)
            : base(new DefaultAuthenticationDetails(userId, token, tokenType),
                   new Uri(string.Format("https://cumulus.services.live.com/{0}/SpacesPhotos", userId)))
        {
        }

        public string UserId
        {
            get { return AuthenticationDetails.UserId; }
        }

        #region Private Methods
        /// <summary>
        /// Parses a PROPFIND response <see cref="Stream"/> into an <see cref="XmlDocument"/> calling the
        /// <paramref name="nodeFound"/> delegate for each PROPSTAT item present.
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="allListed"></param>
        /// <param name="nodeFound"></param>
        private static void ParsePropFindResponse(Stream stream, bool allListed, PropNodeFoundDelegate nodeFound)
        {
            XmlDocument xmld = new XmlDocument();
            NameTable nt = new NameTable();
            XmlNamespaceManager xmlnsm = new XmlNamespaceManager(nt);
            xmlnsm.AddNamespace("D", "DAV:");
            xmlnsm.AddNamespace("c", "http://storage.msn.com/DAV/");
            
            using (StreamReader reader = new StreamReader(stream))
            {
                xmld.LoadXml(reader.ReadToEnd());
            }

            if (xmld.HasChildNodes)
            {
                XmlNodeList xmlnl = xmld.SelectNodes("/D:multistatus/D:response/D:propstat/D:prop", xmlnsm);
                bool first = true;
                foreach (XmlNode xmlnode in xmlnl)
                {
                    if (allListed && first)
                    {
                        // Ignore first item
                        first = false;
                        continue;
                    }

                    nodeFound(xmlnode, xmlnsm);
                }
            }
        }
        #endregion

        #region Photo Methods
        /// <summary>
        /// Gets the photo.
        /// </summary>
        /// <param name="imagePath">The image path.</param>
        /// <returns>The Photo for the requested imagePath</returns>
        /// <example>
        /// <code>
        /// // Create the instance of LivePhotos
        /// LivePhotos m_spaces = new LivePhotos("example@hotmail.com", DAT, AuthenticationToken.DomainAuthenticationToken);
        /// 
        /// // Get the Photo
        /// string imagePath = "myAlbum/winter.jpg";
        /// Photo m_Photo = m_spaces.GetPhoto(imagePath);
        /// </code>
        /// </example>
        public Photo GetPhoto(string imagePath)
        {
            return GetPhoto(new Uri(string.Format("{0}/{1}", RootPath, imagePath)));
        }
        
        /// <summary>
        /// Gets the photo.
        /// </summary>
        /// <param name="photoUrl">The photo URL.</param>
        /// <returns>The Photo for the requested url</returns>
        /// <example>
        /// <code>
        /// // Create the instance of LivePhotos
        /// LivePhotos m_spaces = new LivePhotos("example@hotmail.com", DAT, AuthenticationToken.DomainAuthenticationToken);
        /// 
        /// // Get the Photo
        /// Uri photoUrl = new Uri("https://cumulus.services.live.com/example@hotmail.com/myAlbum/winter.jpg");
        /// Photo m_Photo = m_spaces.GetPhoto(photoUrl);
        /// </code>
        /// </example>
        public Photo GetPhoto(Uri photoUrl)
        {
            if (!IsValidRootPathUrl(photoUrl))
            {
                throw new ArgumentException(string.Format(LiveNetResources.InvalidRootUrlException, "photo"), "photoUrl");
            }

            Photo result = null;

            MakePropFindRequest(photoUrl, false,
                delegate(Stream stream, bool allListed)
                {
                    ParsePropFindResponse(stream, allListed,
                                          delegate(XmlNode xmlnode, XmlNamespaceManager xmlnsm)
                                          {
                                              result = ParsePhoto(xmlnode, xmlnsm);
                                          });
                });

            return result;
        }

        /// <summary>
        /// Obtains a list of all the photos in the album specified.
        /// </summary>
        /// <param name="albumName">The album name</param>
        /// <returns>An array of the Photo elements</returns>
        /// <example>
        /// <code>
        /// // Create the instance of LivePhotos
        /// LivePhotos m_spaces = new LivePhotos("example@hotmail.com", DAT, AuthenticationToken.DomainAuthenticationToken);
        /// 
        /// // Get a list of all the Photos in an Album from the URL.
        /// string albumName = "myAlbum";
        /// 
        /// Photo[] myPhotos = m_spaces.ListPhotos(albumName);
        /// </code>
        /// </example>
        public Photo[] ListPhotos(string albumName)
        {
            return ListPhotos(new Uri(string.Format("{0}/{1}", RootPath, albumName)));
        }

        /// <summary>
        /// Obtains a list of all the photos in the album specified.
        /// </summary>
        /// <param name="albumUrl">The album Url</param>
        /// <returns>An array of the Photo elements</returns>
        /// <example>
        /// <code>
        /// // Create the instance of LivePhotos
        /// LivePhotos m_spaces = new LivePhotos("example@hotmail.com", DAT, AuthenticationToken.DomainAuthenticationToken);
        /// 
        /// // Get a list of all the Photos in an Album from the URL.
        /// Uri albumUrl = new Uri("https://cumulus.services.live.com/example@hotmail.com/myAlbum/");
        /// 
        /// Photo[] myPhotos = m_spaces.ListPhotos(albumUrl);
        /// </code>
        /// </example>
        public Photo[] ListPhotos(Uri albumUrl)
        {
            if (!IsValidRootPathUrl(albumUrl))
            {
                throw new ArgumentException(string.Format(LiveNetResources.InvalidRootUrlException, "album"), "albumUrl");
            }

            List<Photo> result = new List<Photo>();

            MakePropFindRequest(albumUrl, true,
                delegate(Stream stream, bool allListed)
                {
                    ParsePropFindResponse(stream, allListed,
                                          delegate(XmlNode xmlnode, XmlNamespaceManager xmlnsm)
                                          {
                                              result.Add(ParsePhoto(xmlnode, xmlnsm));
                                          });
                });

            return result.ToArray();
        }

        /// <summary>
        /// Gets the image.
        /// </summary>
        /// <param name="imagePath">The image path.</param>
        /// <returns>An Image of the requested image</returns>
        /// <example>
        /// <code>
        /// // Create the instance of LivePhotos
        /// LivePhotos m_spaces = new LivePhotos("example@hotmail.com", DAT, AuthenticationToken.DomainAuthenticationToken);
        /// 
        /// // Gets the Image
        /// string imagePath = "myAlbum/winter.jpg";
        /// Image image = m_spaces.GetImage(imagePath);
        /// </code>
        /// </example>
        public Image GetImage(string imagePath)
        {
            return GetImage(new Uri(string.Format("{0}/{1}", RootPath, imagePath)));
        }

        /// <summary>
        /// Gets an <see cref="Image"/> from server.
        /// </summary>
        /// <param name="imageUrl"></param>
        /// <returns>An Image of the requested image</returns>
        /// <example>
        /// <code>
        /// // Create the instance of LivePhotos
        /// LivePhotos m_spaces = new LivePhotos("example@hotmail.com", DAT, AuthenticationToken.DomainAuthenticationToken);
        /// 
        /// // Gets the Image
        /// Uri imageUrl = new Uri("https://cumulus.services.live.com/example@hotmail.com/myAlbum/winter.jpg");
        /// Image image = m_spaces.GetImage(imageUrl);
        /// </code>
        /// </example>        
        public Image GetImage(Uri imageUrl)
        {
            if (!IsValidRootPathUrl(imageUrl))
            {
                throw new ArgumentException(string.Format(LiveNetResources.InvalidRootUrlException, "image"), "imageUrl");
            }

            Bitmap result = null;

            MakeGetRequest(imageUrl,
                           delegate(Stream stream)
                           {
                               result = new Bitmap(stream);
                           });

            return result;
        }
        
        /// <summary>
        /// Adds the photo.
        /// </summary>
        /// <param name="photoStream">The photo.</param>
        /// <param name="albumUrl">The album href.</param>
        /// <param name="filename">The filename.</param>
        /// <returns>A Photo of the newly added image</returns>
        /// <example>
        /// <code>
        /// // Create the instance of LivePhotos
        /// LivePhotos m_spaces = new LivePhotos("example@hotmail.com", DAT, AuthenticationToken.DomainAuthenticationToken);
        /// 
        /// // Add the new photo
        /// Stream stream = fileUpload.PostedFile.InputStream;
        /// string filename = fileUpload.FileName;
        /// Uri albumUrl = new Uri("https://cumulus.services.live.com/example@hotmail.com/myAlbum/");
        /// Photo winter = myAlbum.AddPhoto(stream, albumUrl, filename);
        /// </code>
        /// </example>
        public Photo AddPhoto(Stream photoStream, Uri albumUrl, string filename)
        {
            if (!IsValidRootPathUrl(albumUrl))
            {
                throw new ArgumentException(string.Format(LiveNetResources.InvalidRootUrlException, "album"), "albumUrl");
            }

            if (photoStream == null)
            {
                throw new ArgumentNullException("photoStream");
            }

            if (string.IsNullOrEmpty(filename))
            {
                throw new ArgumentNullException("filename");
            }

            string tempAlbumUrl = albumUrl.ToString().TrimEnd('/');
            Uri newPhotoUrl = new Uri(string.Format("{0}/{1}", tempAlbumUrl, filename));
            Photo result = null;

            MakePutRequest(newPhotoUrl, photoStream,
                           delegate
                               {
                                   result = GetPhoto(newPhotoUrl);
                               });

            return result;
        }

        /// <summary>
        /// Adds the photo.
        /// </summary>
        /// <param name="photoStream">The photo stream.</param>
        /// <param name="albumUrl">The album URL.</param>
        /// <param name="filename">The filename.</param>
        /// <param name="caption">The caption.</param>
        /// <returns></returns>
        /// <example>
        /// <code>
        /// // Create the instance of LivePhotos
        /// LivePhotos m_spaces = new LivePhotos("example@hotmail.com", DAT, AuthenticationToken.DomainAuthenticationToken);
        /// 
        /// // Add the new photo
        /// Stream stream = fileUpload.PostedFile.InputStream;
        /// string filename = fileUpload.FileName;
        /// string caption = "This is a comment about winter";
        /// Uri albumUrl = new Uri("https://cumulus.services.live.com/example@hotmail.com/myAlbum/");
        /// Photo winter = myAlbum.AddPhoto(stream, albumUrl, filename, caption);
        /// </code>
        /// </example>
        public Photo AddPhoto(Stream photoStream, Uri albumUrl, string filename, string caption)
        {
            Photo photo = AddPhoto(photoStream, albumUrl, filename);

            if (photo != null)
            {
                photo = UpdatePhoto(photo, caption, photo.Name);
            }

            return photo;
        }

        /// <summary>
        /// Deletes the photo.
        /// </summary>
        /// <param name="photo">The photo.</param>
        /// <returns></returns>
        /// <example>
        /// <code>
        /// // Create the instance of LivePhotos
        /// LivePhotos m_spaces = new LivePhotos("example@hotmail.com", DAT, AuthenticationToken.DomainAuthenticationToken);
        /// 
        /// // Get a list of all the Photos in an Album from the URL.
        /// string albumName = "myAlbum";
        /// Photo[] myPhotos = m_spaces.ListPhotos(albumName);
        /// 
        /// foreach(Photo photo in myPhotos)
        /// {
        ///     if(m_spaces.DeletePhoto(photo))
        ///     {
        ///         Console.WriteLine(string.Format("{0} deleted", photo.Name);
        ///     }
        /// }
        /// </code>
        /// </example>
        public bool DeletePhoto(Photo photo)
        {
            if (photo == null)
            {
                throw new ArgumentNullException("photo");
            }

            return MakeDeleteRequest(photo.PhotoUrl);
        }

        /// <summary>
        /// Deletes the photo.
        /// </summary>
        /// <param name="photoUrl">The photo URL.</param>
        /// <returns></returns>
        /// <example>
        /// <code>
        /// // Create the instance of LivePhotos
        /// LivePhotos m_spaces = new LivePhotos("example@hotmail.com", DAT, AuthenticationToken.DomainAuthenticationToken);
        /// 
        /// string address = "https://cumulus.services.live.com/example@hotmail.com/myAlbum/winter.jpg";
        /// if(m_spaces.DeletePhoto(new Uri(address)))
        /// {
        ///     Console.WriteLine("Photo deleted");
        /// }
        /// </code>
        /// </example>
        public bool DeletePhoto(Uri photoUrl)
        {
            if (!IsValidRootPathUrl(photoUrl))
            {
                throw new ArgumentException(string.Format(LiveNetResources.InvalidRootUrlException, "photo"), "photoUrl");
            }

            return MakeDeleteRequest(photoUrl);
        }

        /// <summary>
        /// Updates the photo.
        /// </summary>
        /// <param name="photo">The photo.</param>
        /// <param name="caption">The new caption.</param>
        /// <param name="name"></param>
        /// <returns></returns>
        internal Photo UpdatePhoto(Photo photo, string caption, string name)
        {
            if (MakePropPatchRequest(photo.PhotoUrl, caption, name))
            {
                return GetPhoto(photo.PhotoUrl);
            }

            return null;
        }

        /// <summary>
        /// Parses the photo.
        /// </summary>
        /// <param name="xmlnode">The xmlnode.</param>
        /// <param name="xmlnsm">The XMLNSM.</param>
        /// <returns></returns>
        private Photo ParsePhoto(XmlNode xmlnode, XmlNamespaceManager xmlnsm)
        {
            if (xmlnode == null)
            {
                throw new ArgumentNullException("xmlnode");
            }

            if (xmlnsm == null)
            {
                throw new ArgumentNullException("xmlnsm");
            }

            XmlNode href = xmlnode.SelectSingleNode("D:href", xmlnsm);
            XmlNode name = xmlnode.SelectSingleNode("D:displayname", xmlnsm);
            XmlNode createdOn = xmlnode.SelectSingleNode("D:creationdate", xmlnsm);
            XmlNode lastModified = xmlnode.SelectSingleNode("D:getlastmodified", xmlnsm);
            XmlNode caption = xmlnode.SelectSingleNode("c:Caption", xmlnsm);

            Uri photoUrl = new Uri(href.LastChild.Value);
            Uri albumUrl =
                new Uri(photoUrl.OriginalString.Replace(name.LastChild.Value, string.Empty));

            Photo photo = new Photo(
                this,
                name.LastChild.Value,
                Convert.ToDateTime(createdOn.LastChild.Value),
                Convert.ToDateTime(lastModified.LastChild.Value),
                caption.LastChild == null ? string.Empty : caption.LastChild.Value,
                albumUrl,
                photoUrl);

            return photo;
        }
        #endregion

        #region Album Methods
        /// <summary>
        /// Gets the album.
        /// </summary>
        /// <param name="albumUrl">The album URL.</param>
        /// <returns>An Album from the given URL</returns>
        /// <example>
        /// <code>
        /// // Create the instance of LivePhotos
        /// LivePhotos m_spaces = new LivePhotos("example@hotmail.com", DAT, AuthenticationToken.DomainAuthenticationToken);
        /// 
        /// // Get the album
        /// string address = "https://cumulus.services.live.com/example@hotmail.com/myAlbum/";
        /// Album myAlbum = m_spaces.GetAlbum(new Uri(address));
        /// </code>
        /// </example>
        public Album GetAlbum(Uri albumUrl)
        {
            if (!IsValidRootPathUrl(albumUrl))
            {
                throw new ArgumentException(string.Format(LiveNetResources.InvalidRootUrlException, "album"), "albumUrl");
            }

            Album result = null;

            MakePropFindRequest(albumUrl, false,
                delegate(Stream stream, bool allListed)
                {
                    ParsePropFindResponse(stream, allListed,
                                          delegate(XmlNode xmlnode, XmlNamespaceManager xmlnsm)
                                          {
                                              result = ParseAlbum(xmlnode, xmlnsm);
                                          });
                });

            return result;
        }

        /// <summary>
        /// Gets the album.
        /// </summary>
        /// <param name="albumname">The albumname.</param>
        /// <returns>An Album from the given album name</returns>
        /// <example>
        /// <code>
        /// // Create the instance of LivePhotos
        /// LivePhotos m_spaces = new LivePhotos("example@hotmail.com", DAT, AuthenticationToken.DomainAuthenticationToken);
        /// 
        /// // Get the album
        /// string albumname = "myAlbum";
        /// Album myAlbum = m_spaces.GetAlbum(albumname);
        /// </code>
        /// </example>
        public Album GetAlbum(string albumname)
        {
            if (string.IsNullOrEmpty(albumname))
            {
                throw new ArgumentNullException("albumname");
            }
            Album result = null;
            Album[] albums = ListAlbums();
            foreach (Album album in albums)
            {
                if (album.Name == albumname)
                {
                    result = album;
                    return result;
                }
            }
            return result;
        }
        
        /// <summary>
        /// Obtains a list of all the albums for the user.
        /// </summary>
        /// <returns>An array of Albums for that user</returns>
        /// <example>
        /// <code>
        /// // Create the instance of LivePhotos
        /// LivePhotos m_spaces = new LivePhotos("example@hotmail.com", DAT, AuthenticationToken.DomainAuthenticationToken);
        /// 
        /// // Get the list of albums
        /// Album[] myAlbums = m_spaces.ListAlbums();
        /// foreach (Album album in myAlbums)
        /// {
        ///     Console.WriteLine(album.Name);
        /// }
        /// </code>
        /// </example>
        public Album[] ListAlbums()
        {
            List<Album> result = new List<Album>();

            MakePropFindRequest(RootPath, true,
                delegate(Stream stream, bool allListed)
                {
                    ParsePropFindResponse(stream, allListed,
                                          delegate(XmlNode xmlnode, XmlNamespaceManager xmlnsm)
                                          {
                                              result.Add(ParseAlbum(xmlnode, xmlnsm));
                                          });
                });

            return result.ToArray();
        }

        /// <summary>
        /// Creates a new album.
        /// </summary>
        /// <param name="name">The name the new folder will be created with.</param>
        /// <returns>The newly created Albums' details, or an exception if unsuccessfull.</returns>
        /// <example>
        /// <code>
        /// // Create the instance of LivePhotos
        /// LivePhotos m_spaces = new LivePhotos("example@hotmail.com", DAT, AuthenticationToken.DomainAuthenticationToken);
        /// 
        /// // Create the new album
        /// string name = "New Album";
        /// Album newAlbum = m_spaces.CreateAlbum(name);
        /// if(newAlbum != null)
        /// {
        ///     Console.WriteLine(string.Format("\"{0}\" has been created.", newAlbum.Name);
        /// }
        /// </code>
        /// </example>
        public Album CreateAlbum(string name)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentNullException("name");
            }

            Album result = null;
            Uri newFolderUrl = new Uri(RootPath.OriginalString + "/" + name);

            if (MakeMkcolRequest(newFolderUrl))
            {
                MakePropFindRequest(newFolderUrl, false,
                                    delegate(Stream stream, bool allListed)
                                    {
                                        ParsePropFindResponse(stream, allListed,
                                                              delegate(XmlNode xmlnode, XmlNamespaceManager xmlnsm)
                                                              {
                                                                  result = ParseAlbum(xmlnode, xmlnsm);
                                                              });
                                    });
            }
            else
            {
                throw new LiveNetException(
                    string.Format(CultureInfo.InvariantCulture, LiveNetResources.CreateAlbumException, name));
            }

            return result;
        }

        /// <summary>
        /// Deletes the album.
        /// </summary>
        /// <param name="album">The album.</param>
        /// <returns></returns>
        /// <example>
        /// <code>
        /// // Create the instance of LivePhotos
        /// LivePhotos m_spaces = new LivePhotos("example@hotmail.com", DAT, AuthenticationToken.DomainAuthenticationToken);
        /// 
        /// // Get the list of albums
        /// Album[] myAlbums = m_spaces.ListAlbums();
        /// foreach (Album album in myAlbums)
        /// {
        ///     if(m_spaces.DeleteAlbum(album))
        ///     {
        ///         Console.WriteLine(string.Format("{0} deleted.", album.Name);
        ///     }
        /// }
        /// </code>
        /// </example>
        public bool DeleteAlbum(Album album)
        {
            if (album == null)
            {
                throw new ArgumentNullException("album");
            }

            return DeleteAlbum(album.AlbumUrl);
        }

        /// <summary>
        /// Deletes the album.
        /// </summary>
        /// <param name="albumName">Name of the album.</param>
        /// <returns></returns>
        /// <example>
        /// <code>
        /// // Create the instance of LivePhotos
        /// LivePhotos m_spaces = new LivePhotos("example@hotmail.com", DAT, AuthenticationToken.DomainAuthenticationToken);
        /// 
        /// // Delete an album
        /// string albumname = "goodbye";
        /// if(m_spaces.DeleteAlbum(albumname))
        /// {
        ///     Console.WriteLine(string.Format("{0} deleted.", albumname));
        /// }
        /// </code>
        /// </example>
        public bool DeleteAlbum(string albumName)
        {
            if (string.IsNullOrEmpty(albumName))
            {
                throw new ArgumentNullException("albumName");
            }

            Uri albumUrl = new Uri(string.Format("{0}/{1}", RootPath, albumName));

            return DeleteAlbum(albumUrl);
        }

        /// <summary>
        /// Deletes the album.
        /// </summary>
        /// <param name="albumUrl">The album URL.</param>
        /// <returns></returns>
        /// <example>
        /// <code>
        /// // Create the instance of LivePhotos
        /// LivePhotos m_spaces = new LivePhotos("example@hotmail.com", DAT, AuthenticationToken.DomainAuthenticationToken);
        /// 
        /// // Delete an album
        /// string address = "https://cumulus.services.live.com/example@hotmail.com/myAlbum/";
        /// if(m_spaces.DeleteAlbum(new Uri(address)))
        /// {
        ///     Console.WriteLine(string.Format("{0} deleted.", albumname));
        /// }
        /// </code>
        /// </example>
        public bool DeleteAlbum(Uri albumUrl)
        {
            if (!IsValidRootPathUrl(albumUrl))
            {
                throw new ArgumentException(string.Format(LiveNetResources.InvalidRootUrlException, "album"), "albumUrl");
            }

            return MakeDeleteRequest(albumUrl);
        }

        private Album ParseAlbum(XmlNode xmlnode, XmlNamespaceManager xmlnsm)
        {
            if (xmlnode == null)
            {
                throw new ArgumentNullException("xmlnode");
            }

            if (xmlnsm == null)
            {
                throw new ArgumentNullException("xmlnsm");
            }

            XmlNode href = xmlnode.SelectSingleNode("D:href", xmlnsm);
            XmlNode name = xmlnode.SelectSingleNode("D:displayname", xmlnsm);
            XmlNode createdOn = xmlnode.SelectSingleNode("D:creationdate", xmlnsm);
            XmlNode lastModified = xmlnode.SelectSingleNode("D:getlastmodified", xmlnsm);

            Album album = new Album(
                this,
                name.LastChild.Value,
                href.LastChild.Value,
                Convert.ToDateTime(createdOn.LastChild.Value),
                Convert.ToDateTime(lastModified.LastChild.Value),
                new Uri(href.LastChild.Value));

            return album;
        }
        #endregion
    }
}
